# -*- coding: utf-8 -*-
__author__ = 'lztkdr'
__date__ = '2020/4/15 16:45'

from matplotlib import pyplot as plt
from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestClassifier
from sklearn.tree import DecisionTreeClassifier, export_graphviz
from sklearn.feature_extraction import DictVectorizer
from sklearn.model_selection import train_test_split
import pandas as pd

"""
	决策树：
		决策树思想的来源非常朴素，程序设计中的条件分支结构就是if-then结构，最早的决策树就是利用这类结构分割数据的一种分类学习方法

	信息熵越大，信息不确定性越大。
	决策树的划分依据之一 - 信息增益。
	信息增益表示得知特征X的信息而使得类Y的信息的不确定性减少的程度。
	=========================================================================
	构建决策树的三个问题：
		1）从哪个属性开始或者说选择哪个属性作为根节点
		2）选择哪个属性作为后继节点
		3）什么时候停止并得到目标值

	决策树算法的基本思想：
		选择 最优属性 划分当前样本集合并把这个属性作为决策树的一个节点。
		不断重复这个过程构造后继节点。

	=========================================================================
		常见决策树使用的算法：
		ID3
			信息增益 最大的准则
		C4.5
			信息增益比 最大的准则
		CART
			回归树: 平方误差 最小
			分类树: 基尼系数   最小的准则 在sklearn中可以选择划分的默认原则，用 基尼系数 划分更加仔细。
	=========================================================================
	class sklearn.tree.DecisionTreeClassifier(criterion=’gini’, max_depth=None,random_state=None)
	决策树分类器
	criterion:默认是’gini’系数，也可以选择信息增益的熵’entropy’，默认 gini 就是指 基尼系数，entropy 指信息熵。
	max_depth:树的深度大小
	random_state:随机数种子

	method:
		decision_path:返回决策树的路径
	=========================================================================
	优点：
		简单的理解和解释，树木可视化。
		需要很少的数据准备，其他技术通常需要数据归一化，

	缺点：
		决策树学习者可以创建不能很好地推广数据的过于复杂的树，这被称为过拟合（在训练集分数可以很高，但是在测试集分数却很低）。
		决策树可能不稳定，因为数据的小变化可能会导致完全不同的树被生成

	改进：
		减枝cart算法（构造函数的参数可以调整(https://www.cnblogs.com/pinard/p/6056319.html)）
				max_depth: 树的深度大小
				min_samples_split: 内部节点再划分所需最小样本数
				min_samples_leaf: 叶子节点最少样本数

		随机森林

	应用：
		企业重要决策，由于决策树很好的分析能力，在决策过程应用较多。

"""
"""
	随机森林:
		是一种集成学习方法。
		定义：在机器学习中，随机森林是一个包含多个决策树的分类器，并且其输出的类别是由个别树输出的类别的众数而定。

	集成学习方法：
		集成学习通过建立几个模型组合的来解决单一预测问题。它的工作原理是生成多个分类器/模型，各自独立地学习和作出预测。这些预测最后结合成单预测，因此优于任何一个单分类的做出预测。

	=========================================================================

	from sklearn.ensemble import RandomForestClassifier

	class sklearn.ensemble.RandomForestClassifier(n_estimators=10, criterion=’gini’,
	 max_depth=None, bootstrap=True, random_state=None)
	随机森林分类器
	n_estimators：integer，optional（default = 10） 森林里的树木数量 120,200,300,500,800,1200
	criteria：string，可选（default =“gini”）分割特征的测量方法
	n_jobs：用于拟合和预测的并行运行的工作（作业）数量。如果值为-1，那么工作数量被设置为核的数量。
	max_depth：integer或None，可选（默认=无）树的最大深度 5,8,15,25,30
	max_features: auto,每个决策树的最大特征数量
	bootstrap：boolean，optional（default = True）是否在构建树时使用放回抽样

	随机森林的优点:
		在当前所有算法中，具有极好的准确率
		能够有效地运行在大数据集（体现在样本数、特征数）上
		能够处理具有高维特征的输入样本，而且不需要降维
		能够评估各个特征在分类问题上的重要性
		对于缺省值问题也能够获得很好得结果
		
	随机森林的缺点:
		没有缺点，唯一缺点就是没有找对最优参数。
		
	------------------------------------------------------------------------------
    分类模型 过拟合 怎么处理？
		决策树的话，可以用叶子节点设置的大一些（max_depth）。
		用随机森林。	
"""

"""
export_graphviz
	决策树的结构、本地保存

"""

# Pycharm输出窗口有省略号，数据显示不全解决方法
pd.set_option('display.max_rows', 10)
pd.set_option('display.max_columns', 200)
pd.set_option('display.width', 1000)


# 如果要显示中文标题和坐标表示，需要添加以下两行代码
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 关闭 SettingWithCopyWarning 警告
pd.set_option('mode.chained_assignment', None)

print('pd.__version__:', pd.__version__)

print('============================================================================================================')

"""
http://biostat.mc.vanderbilt.edu/wiki/pub/Main/DataSets/titanic.html
http://biostat.mc.vanderbilt.edu/wiki/pub/Main/DataSets/titanic.txt

泰坦尼克号数据
	在泰坦尼克号和titanic2数据帧描述泰坦尼克号上的个别乘客的生存状态。
	在泰坦尼克号的数据帧不包含从剧组信息，但它确实包含了乘客的一半的实际年龄。
	关于泰坦尼克号旅客的数据的主要来源是百科全书Titanica。这里使用的数据集是由各种研究人员开始的。
	其中包括许多研究人员创建的旅客名单，由Michael A. Findlay编辑。

	我们提取的数据集上的变量是pclass，幸存，姓名，年龄，登船，住址，住所，机票，船期和性别。pclass指的是乘客舱（第一，第二，第三），是社会经济舱的代表。年龄以年为单位，有些婴儿具有分数值
	乘坐班是指乘客班（1，2，3），是社会经济阶层的代表。
	其中age数据存在缺失。

	预测船员乘客 是否幸存(是生是死) survived ？

	survived  =》 目标值 （0死亡、1幸存）

"""

df = None


def init():
    # file_name = 'http://biostat.mc.vanderbilt.edu/wiki/pub/Main/DataSets/titanic.txt'
    file_name = './data/titanic.txt'
    global df
    df = pd.read_csv(file_name)
    print(df.info())
    print("==========================================================")
    print(df.head())
    print("==========================================================")
    print('pclass:', df.pclass.unique())
    print("==========================================================")
    return None


def decisiontree():
    # 提取 与 特征目标值 密切关联的 特征
    # 仓位、年龄、性别 最为重要
    x = df[['pclass', 'age', 'sex']]
    # 目标值
    y = df['survived']

    # 缺失值处理
    x['age'].fillna(x['age'].mean(), inplace=True)

    # 分割数据集到训练集合测试集
    # random_state 若为整数时，每次生成的数据都相同,若为None时，每次生成的数据都是随机，可能不一样
    x_train, x_test, y_train, y_test = train_test_split(
        x, y, test_size=0.25, random_state=0)

    # 进行处理（特征工程）特征-》类别(pclass/sex)-》one_hot编码
    dvr = DictVectorizer(sparse=False)

    # df 转换为 字典格式
    print(
        'x_train.to_dict(orient="records"):\n',
        x_train.to_dict(
            orient="records")[
            :5],
        '......')
    print("==========================================================")

    x_train = dvr.fit_transform(x_train.to_dict(orient="records"))
    print('get_feature_names:', dvr.get_feature_names())
    print("==========================================================")

    x_test = dvr.transform(x_test.to_dict(orient="records"))
    print('x_train:\n', x_train)
    print("==========================================================")

    # 用决策树进行预测
    model = DecisionTreeClassifier()

    model.fit(x_train, y_train)

    # 预测准确率
    print("预测的准确率：", model.score(x_test, y_test))

    """
		from sklearn.tree import export_graphviz
		先用export_graphviz方法将生成的模型导出到一个.dot文件中，然后需要安装graphviz，并转换成图片格式

		安装 graphviz，一种是msi安装，一种下载压缩包后解压来用
		解压后，将生成的 .dot类型的文件复制到 dot.exe所在目录（graphviz的bin目录下），运行以下命令，便可得到一个png图片
		dot -Tpng titanic_2.dot -o titanic.png
	"""
    # 保存导出决策树的结构
    # feature_names 对应 get_feature_names ，可以重命名
    export_graphviz(
        model,
        out_file="./tree.dot",
        feature_names=[
            '年龄',
            'pclass=1st',
            'pclass=2nd',
            'pclass=3rd',
            '女性',
            '男性'])

    print("==============================================================================================")

    # 随机森林进行预测 （超参数调优）
    # n_jobs：用于拟合和预测的并行运行的工作（作业）数量。如果值为-1，那么工作数量被设置为核的数量。
    rf = RandomForestClassifier(n_jobs=-1)

    param = {
        "n_estimators": [
            120, 200, 300, 500, 800, 1200],
        "max_depth": [
            5, 8, 15, 25, 30]
    }

    # 网格搜索与交叉验证
    gc = GridSearchCV(rf, param_grid=param, cv=2)

    gc.fit(x_train, y_train)

    print("准确率：", gc.score(x_test, y_test))

    print("查看选择的参数模型：", gc.best_params_)

    return None


if __name__ == "__main__":
    init()
    # 决策树(DecisionTreeClassifier)、随机森林(RandomForestClassifier) 实现
    decisiontree()
